在日常开发中,我们经常使用到系统的导航,来完成各页面的交互跳转,这是再正常不过且用到的基础功能。众所周知,我们也可以在具体的页面禁用系统导航,一般都是通过设置interactivePopGestureRecognizer
是否开启来实现,不过,我们必须注意系统导航在根页面时,未经处理,用户再测滑则会导致app
卡住甚至卡死,这个问题在之前的一篇文章中也有说过,并且也说了如何监听用户侧滑释放的方法,以便我们能够做一些额外操作。文章链接🔗: iOS interactivePopGestureRecognizer卡住&手势滑动监听。
尽管系统的导航已满足大部分开发需求,但是用户的体验可能不是很好。之前的那篇文章虽然解决了以下两个问题:
- 导航在根页面时因为测滑卡死。
- 导航测滑监听的解决方案。
但是发现还是不够完美,因此,本文主要结合实际,实现系统的侧滑导航、全屏侧滑导航、导航侧滑监听的一体化方案,解决以下问题:
- 避免在根页面测滑卡死。
- 导航侧滑的更好的监听解决方案。
- 实现全屏测滑导航。
系统测滑导航
首先,在子类BaseNavigationController
中设置手势代理和导航代理
1 |
|
为了保证每一次push
操作,都设置开启系统导航,我们复写父类UINavigationController
的方法
1 | // Override super method to initialize interactive pop gesture here. |
这样的话,就保证了每次即将push
到下一个页面时,系统测滑手势是被开启的。
实现UINavigationControllerDelegate
的方法
1 | #pragma mark UINavigationControllerDelegate |
这样就可以了吗?经测试,我们发现,虽然以上方案解决了根页面测滑卡死的问题,但是有一个Bug
:A页面 -> B页面 -> C页面
,如果C页面
禁用测滑手势,即设置self.navigationController.interactivePopGestureRecognizer.enabled = NO;
,此时返回到B页面
时,发现B页面
的测滑手势也被禁用了,即无法侧滑到A页面
。这是因为侧滑手势的属性设置时全局性的,都是导航控制器的同一个设置,所以才出现了这么一个Bug
。
既然C页面
设置了禁用侧滑手势,导致C页面
销毁时,所有的测滑手势都被禁用,那能不能在C页面
消失的时候再把测滑手势开启呢?答案显然是可以的,比如
1 | - (void)viewWillDisappear:(BOOL)animated { |
但是,如果有多个页面都需要禁用测滑手势,其他都是开启,我们要每个页面都这样写一遍?显然太麻烦,而且都是些冗余代码。因此,我的思路是,在父类中用一个变量保存每一个控制器的侧滑手势,如下
1 | @implementation BaseNavigationController { |
这样就完美地解决了测滑手势设置的相互之间的影响问题。接下来,我们看一下如何实现全屏手势侧滑。
全屏测滑导航
我们知道,系统测滑手势必然要依赖手势识别器,而在导航控制器的源码中
1 | @property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer |
可知,interactivePopGestureRecognizer
就是手势的核心,我们在viewDidLoad
打印一下
1 | - (void)viewDidLoad { |
发现这个手势中有一个handleNavigationTransition:
方法,从字面意思就是操作导航过渡。既然如此,我们就直接对这个方法进行操作,添加到我们当前导航的手势上面
1 | - (void)configureNavigationGestures { |
然后实现手势代理UIGestureRecognizerDelegate
1 | #pragma mark - UIGestureRecognizerDelegate |
这样就实现了全屏侧滑导航了。
侧滑手势监听
上一篇文章中,iOS interactivePopGestureRecognizer卡住&手势滑动监听,尽管可以通过通知监听用户侧滑导航,但是实在是太不方便了,因为不仅要在导航的父类添加要监听的页面的字符串,还要在相应的页面做监听,还要移除监听,简直太繁琐了!
于是,针对这个问题,有了另一个思路,通过获取到正在测滑导航的页面,判断是否实现了相应的侧滑导航结束的方法,如果有,直接调用,如果没有,则什么也不做,太完美了!
但是有一个问题,我们如何知道当前正在侧滑的页面是哪一个呢?通过navigationController.topViewController
并获取不到正在侧滑的页面,于是经过查看源码发现,系统提供的UIViewControllerTransitionCoordinatorContext
有一个方法- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;
可以获取到当前正在侧滑导航的UIViewController
,这样就好实现了。
一般情况下,我们的所有页面都会有一个基类BaseViewController
,我们在BaseViewController.h
中添加
1 | - (void)cl_popGestureDidEnd; |
BaseViewController.m
1 | /** |
在导航基类BaseNavigationController
中导入
1 | #import "BaseNavigationController.h" |
在监听导航侧滑的UINavigationControllerDelegate
的方法中,实现
1 | #pragma mark - UINavigationControllerDelegate |
我们在想要监听侧滑导航的页面,比如SecondViewController(继承自BaseViewController)
,复写
1 | - (void)cl_popGestureDidEnd { |
这样,就实现了侧滑导航的监听,如果侧滑结束的时候,页面消失,即返回到上一个页面,cl_popGestureDidEnd
方法就会被调用。
如何不想导入BaseViewController
怎么办呢?也可以通过runtime
运行时来做处理
1 | - (void)navigationController:(UINavigationController *)navigationController |
在
BaseViewController.h
和BaseViewController.m
中声明和实现cl_popGestureDidEnd
方法,主要是为了子类可以直接调用,能够直接联想出来。如果使用runtime
运行时的方案来实现的话,你也可以完全不用在BaseViewController
中添加cl_popGestureDidEnd
方法,直接在子类中实现cl_popGestureDidEnd
方法即可。当然,不导入BaseViewController
也可以直接使用runtime
来调用,这里就不赘述了。
小结
本文主要介绍了全屏侧滑的实现,以及如何避免侧滑导航到根视图控制器app
卡死的解决方案,最后也完美地实现如何监听用户侧滑导航的方法。全屏侧滑导航在一定程度上优化了用户体验,侧滑导航监听可以在一些特殊情况下,实现我们的一些特殊操作。
最后,本文将以上内容做了分开讲解,实际上我已经在Demo
中将上面的代码完全整合到了一起,代码非常简单,总的一百多行代码。比如在Demo
中有如下属性
1 | /// Whether to use system pop gesture. If NO, full-screen pop gesture will be set. |
使用的时候,直接修改这两个属性即可,其余代码基本上直接使用,不需要修改!例如,想使用系统侧滑导航,直接修改
1 | static const BOOL useSystemGesture = YES; |
想使用全屏侧滑导航
1 | static const BOOL useSystemGesture = NO; |
默认开启全局侧滑手势
1 | static const BOOL popGestureEnabled = YES; |
默认关闭全局侧滑手势
1 | static const BOOL popGestureEnabled = NO; |
附录
Demo
地址:CLNavigationPopGesture-Example。- 个人博客
- 个人简书